/*
	Some code logic was copied from Open Market FastCGI C++ library (fcgi-2.4.0) - file "os_win32.c".
	To avoid licensing issues attached original license file - "LICENSE.TERMS".
*/

#include "fastcgi_util.hpp"

#include "aconnect/util.time.hpp"
#include "aconnect/util.string.hpp"

namespace fastcgi 
{ 
	SocketDescriptor ApplicationManager::connectToFcgiApplication (int descriptor, int readWriteTimeout) throw (fastcgi_error)
	{
		ApplicationConnection con = getConnection(descriptor);
		return connectToFcgiApplication (con, readWriteTimeout);
	}
	
	int ApplicationManager::registerConnection (SocketDescriptor desc, 
		aconnect::port_type port,
		aconnect::string_constptr pipeName,
		int desiredIndex)
	{
		boost::mutex::scoped_lock lock (_registrationMutex);
		
		assert (desc.mode != UnusedConnection);
#ifdef WIN32		
		assert (desc.value != 0);
#endif
		int index = -1;
	
		// If desiredIndex is set, try to get this entry (this is used for mapping stdio handles).  
		// If this is not available, find a the first empty slot.
	    
		if (isDescriptorValid (desiredIndex)) {
			if (_connectionsList[desiredIndex].sid.mode == UnusedConnection)
				index = desiredIndex;
		} 

		const size_t listSize = _connectionsList.size();

		if (index == -1 && listSize) 
		{
			for (size_t ndx = 0; ndx<listSize; ++ndx) {
				if (_connectionsList[ndx].sid.mode == UnusedConnection) {
					index = ndx;
					break;
				}
			}
		}

		if (index == -1) {
			_connectionsList.push_back (ApplicationConnection());
			index = listSize;
		}
	    
		assert (index != -1);
		
		ApplicationConnection& info = _connectionsList[index];

		info.reset();
		info.sid = desc;
		info.port = port;

		if (pipeName)
			info.path = pipeName;
		
		return index;
	}


	//==================================================================//
	/** Free I/O descriptor entry.
	*/
	void ApplicationManager::freeConnection(int fd)
	{
		boost::mutex::scoped_lock lock (_registrationMutex);

		// do not create empty record
		if (!isDescriptorValid (fd))
			return;

		ApplicationConnection& info = _connectionsList[fd];
		if (info.sid.mode == fastcgi::UnusedConnection)
			return;

		info.reset();
	}

	//==================================================================//

	void ApplicationManager::loadFcgiAppInfo (const SocketDescriptor& sd, FcgiApplicationInfo& info) throw (fastcgi_error)
	{
		const int RecordLength = 56; // 8 (HeaderLenght) + (14+1+1) + (13+1+1) + (15+1+1), padding not need

		byte_type request[RecordLength] = {0,}; 
    
		byte_type* requestPos = &request[HeaderLenght];
		byte_type* requestEnd = &request[RecordLength];

		int written = support::writeKeyValuePair (requestPos, requestEnd - requestPos, FcgiApplicationProperties::FcgiMaxConns);
		assert (written = strlen(FcgiApplicationProperties::FcgiMaxConns) + 2);
		requestPos += written;

		written = support::writeKeyValuePair (requestPos, requestEnd - requestPos, FcgiApplicationProperties::FcgiMaxReqs);
		assert (written = strlen(FcgiApplicationProperties::FcgiMaxReqs) + 2);
		requestPos += written;

		written = support::writeKeyValuePair (requestPos, requestEnd - requestPos, FcgiApplicationProperties::FcgiMpxsConns);
		assert (written = strlen(FcgiApplicationProperties::FcgiMpxsConns) + 2);
	
		support::formatFcgiHeader (*((Header*) request),
			FcgiMessage::GetValues,
			FcgiNullRequestId,
			RecordLength - HeaderLenght,
			0);

		write (sd, request, RecordLength, true);

		// read command response
		InputStream input (sd);
		input.fillBuffer();

		aconnect::string valuesData;
		
		Header responseHeader;

		// Suppose that only one record will be returned - isReadyForRead returns true for empty input (((
		// while (isReadyForRead (descriptor)) { }

		if (!input.readHeader (responseHeader))
			throw fastcgi_error(FcgiError::ProtocolError, "FASTCGI: GetValues response corrupted.");

		if (responseHeader.type != FcgiMessage::GetValuesResult)
			throw fastcgi_error(FcgiError::InvalidMessageType, "FASTCGI: invalid type response type on GetValues request.");
		
		int	contentLen = (responseHeader.contentLengthB1 << 8) + responseHeader.contentLengthB0;
		int fullLength = contentLen + responseHeader.paddingLength;

		aconnect::string dataBlock = input.read (fullLength);
		if (dataBlock.size() != (size_t) fullLength)
			throw fastcgi_error(FcgiError::ProtocolError, "FASTCGI: GetValues response corrupted.");
		
		if (responseHeader.paddingLength == 0) {
			if (valuesData.empty())
				valuesData.swap (dataBlock);
			else
				valuesData += dataBlock;
		} else {
			valuesData += dataBlock.substr(0, contentLen);
		}

	
		aconnect::str2str_map settings = support::parseKeyValuePairs ((const byte_type*) valuesData.c_str(), valuesData.size());

		info.MaxConnsCount = aconnect::util::getItemFromMap(settings, FcgiApplicationProperties::FcgiMaxConns, info.MaxConnsCount);
		info.MaxReqsCount = aconnect::util::getItemFromMap(settings, FcgiApplicationProperties::FcgiMaxReqs, info.MaxReqsCount);
		info.CanMultiplexConns = aconnect::util::getItemFromMapBool(settings, FcgiApplicationProperties::FcgiMpxsConns, info.CanMultiplexConns);
	}

	void ApplicationManager::writeBeginRequestMessage (const SocketDescriptor& sd, int requestId, bool keepConnection) throw (fastcgi_error)
	{
		fastcgi::BeginRequestRecord request;
		
		support::formatFcgiHeader (request.header,
			FcgiMessage::BeginGequest,
			requestId,
			sizeof (fastcgi::BeginRequestBody),
			0);
		
		support::formatBeginRequestBody(request.body,
			ApplicationRole::Responder,
			keepConnection);

		write (sd, (fastcgi::byte_type*) &request, sizeof (fastcgi::BeginRequestRecord));
	}
	
	void ApplicationManager::writePadding (const SocketDescriptor& sd, int paddingLength) throw (fastcgi_error)
	{
		if (paddingLength <= 0)
			return;

		static byte_type buffer[8] = {0,};
		
		write (sd, &buffer[0], paddingLength);
	}

} // namespace fastcgi



